#pragma once

#include "sfc/core/mem.h"

namespace sfc::num {

[[sfc_inline]] constexpr inline auto nan() -> f32 {
  return __builtin_nanf("");
}

[[sfc_inline]] constexpr inline auto inf() -> f32 {
  return __builtin_inff();
}

template <trait::Float T>
[[sfc_inline]] inline auto isnan(T x) -> bool {
  return __builtin_isnan(x);
}

template <trait::Float T>
[[sfc_inline]] inline auto isinf(T x) -> bool {
  return __builtin_isinf(x);
}

template <trait::Float T>
[[sfc_inline]] inline auto fabs(T x) -> T {
  if constexpr (__is_same(T, f32)) {
    return __builtin_fabsf(x);
  } else {
    return __builtin_fabs(x);
  }
}

template <trait::Float T>
[[sfc_inline]] inline auto round(T x) -> T {
  if constexpr (__is_same(T, f32)) {
    return __builtin_roundf(x);
  } else {
    return __builtin_round(x);
  }
}

template <trait::Float T>
[[sfc_inline]] inline auto floor(T x) -> T {
  if constexpr (__is_same(T, f32)) {
    return __builtin_floorf(x);
  } else {
    return __builtin_floor(x);
  }
}

template <trait::Float T>
[[sfc_inline]] inline auto ceil(T x) -> T {
  if constexpr (__is_same(T, f32)) {
    return __builtin_ceilf(x);
  } else {
    return __builtin_ceil(x);
  }
}

template <trait::Float T>
[[sfc_inline]] inline auto trunc(T x) -> T {
  if constexpr (__is_same(T, f32)) {
    return __builtin_truncf(x);
  } else {
    return __builtin_trunc(x);
  }
}

template <trait::Float T>
[[sfc_inline]] inline auto fmod(T x, T y) -> T {
  if constexpr (__is_same(T, f32)) {
    return __builtin_fmodf(x, y);
  } else {
    return __builtin_fmod(x, y);
  }
}

template <trait::Float T>
[[sfc_inline]] inline auto fmin(T x, T y) -> T {
  if constexpr (__is_same(T, f32)) {
    return __builtin_fminf(x, y);
  } else {
    return __builtin_fmin(x, y);
  }
}

template <trait::Float T>
[[sfc_inline]] inline auto fmax(T x, T y) -> T {
  if constexpr (__is_same(T, f32)) {
    return __builtin_fmaxf(x, y);
  } else {
    return __builtin_fmax(x, y);
  }
}

[[sfc_inline]] inline auto flt_eq(f64 a, f64 b, u32 err = 4) -> bool {
  if (__builtin_isnan(a) || __builtin_isnan(b)) {
    return false;
  }
  if (a == b) {
    return true;
  }

  if (a < 0) {
    a = __builtin_fabs(a);
    b = __builtin_fabs(b);
  }
  auto ua = mem::bit_cast<u64>(a);
  auto ub = mem::bit_cast<u64>(b);
  const auto uc = ua < ub ? ub - ua : ua - ub;
  return uc < err;
}

}  // namespace sfc::num
